package mirrormonkey.util.annotations.parsing.memberlevel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import junit.framework.Assert;
import mirrormonkey.util.annotations.control.AnnotationOverride;
import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;
import mirrormonkey.util.annotations.parsing.ConstructorIR;
import mirrormonkey.util.annotations.parsing.MemberIR;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Contains test cases concerning <tt>ConstructorIR</tt>, which is a simple IR
 * class similar to <tt>MethodIR</tt>, only that it collects constructors
 * instead of methods.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class ConstructorIRTest {

	public static AnnotationParser parser;

	@BeforeClass
	public static void setUpBeforeClass() {
		parser = new AnnotationParser();
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation {
	}

	public static class DeclaringConstructors {

		@IRClass(ConstructorIR.class)
		public DeclaringConstructors() {
		}

		public DeclaringConstructors(Object param) {
		}

	}

	/**
	 * Tests if <tt>ConstructorIR</tt> collects constructors and only
	 * constructors annotated with <tt>IRClass</tt>.
	 */
	@Test
	public void testCollectConstructor() {
		ClassIR ir = parser.parseClass(DeclaringConstructors.class, null);
		Assert.assertTrue(ir.memberIRs.get(Field.class).isEmpty());
		Assert.assertTrue(ir.memberIRs.get(Method.class).isEmpty());
		Assert.assertEquals(1, ir.memberIRs.get(Constructor.class).size());
		MemberIR mir = ir.memberIRs.get(Constructor.class).iterator().next();
		Assert.assertEquals(ConstructorIR.class, mir.getClass());
		Assert.assertEquals(0,
				((ConstructorIR) mir).getParameterAnnotations().length);
	}

	public static class ExtendDeclaringConstructors extends
			DeclaringConstructors {

		public ExtendDeclaringConstructors() {
		}

		@IRClass(ConstructorIR.class)
		public ExtendDeclaringConstructors(Object param) {
		}

	}

	/**
	 * Tests if <tt>ConstructorIR</tt> correctly overrides constructors with
	 * more recent ones.
	 */
	@Test
	public void testInheritConstructor() {
		ClassIR ir = parser.parseClass(ExtendDeclaringConstructors.class, null);
		Assert.assertTrue(ir.memberIRs.get(Field.class).isEmpty());
		Assert.assertTrue(ir.memberIRs.get(Method.class).isEmpty());
		Assert.assertEquals(2, ir.memberIRs.get(Constructor.class).size());
		boolean[] found = new boolean[] { false, false };
		for (MemberIR mir : ir.memberIRs.get(Constructor.class)) {
			ConstructorIR cir = (ConstructorIR) mir;
			found[cir.getParameterAnnotations().length] = true;
		}
		int i = 0;
		for (boolean b : found) {
			Assert.assertTrue("" + i, b);
			i++;
		}
	}

	public static class OverrideDeclaringConstructors extends
			ExtendDeclaringConstructors {

		@AnnotationOverride
		public OverrideDeclaringConstructors() {
		}

		@AnnotationOverride
		public OverrideDeclaringConstructors(Object param) {
		}

	}

	/**
	 * Tests if it is possible to remove <tt>ConstructorIRs</tt> via
	 * <tt>AnnotationOverride</tt>
	 */
	@Test
	public void testOverrideConstructor() {
		ClassIR ir = parser.parseClass(OverrideDeclaringConstructors.class,
				null);
		Assert.assertTrue(ir.memberIRs.get(Field.class).isEmpty());
		Assert.assertTrue(ir.memberIRs.get(Method.class).isEmpty());
		Assert.assertTrue(ir.memberIRs.get(Constructor.class).isEmpty());
	}

	public static class DeclareAnnotation {

		@CustomAnnotation
		@IRClass(value = ConstructorIR.class, startCollect = CustomAnnotation.class)
		public DeclareAnnotation() {
		}

	}

	/**
	 * Tests if it is possible to collect annotations from constructors.
	 */
	@Test
	public void testCollectAnnotation() {
		ClassIR ir = parser.parseClass(DeclareAnnotation.class, null);
		Assert.assertEquals(1, ir.memberIRs.get(Constructor.class).size());
		ConstructorIR cir = (ConstructorIR) ir.memberIRs.get(Constructor.class)
				.iterator().next();
		Assert.assertEquals(1, cir.collectedAnnotations.size());
		Assert.assertNotNull(cir.collectedAnnotations
				.get(CustomAnnotation.class));
	}

	public static class FirstLevel {

		@IRClass(ConstructorIR.class)
		public FirstLevel() {
		}

		@IRClass(ConstructorIR.class)
		public FirstLevel(Object param) {
		}

		@IRClass(ConstructorIR.class)
		public FirstLevel(Object param, Object param2) {
		}
	}

	public static class SecondLevel extends FirstLevel {

		public SecondLevel() {
		}

		public SecondLevel(Object param) {
		}

	}

	/**
	 * Tests if constructors that are not re-declared are removed after parsing
	 * the class hierarchy.
	 */
	@Test
	public void testConstructorCleanup() {
		ClassIR ir = parser.parseClass(SecondLevel.class, null);
		Assert.assertTrue(ir.memberIRs.get(Field.class).isEmpty());
		Assert.assertTrue(ir.memberIRs.get(Method.class).isEmpty());
		Assert.assertEquals(2, ir.memberIRs.get(Constructor.class).size());
		boolean[] found = new boolean[] { false, false };
		for (MemberIR mir : ir.memberIRs.get(Constructor.class)) {
			ConstructorIR cir = (ConstructorIR) mir;
			found[cir.getParameterAnnotations().length] = true;
		}
		int i = 0;
		for (boolean b : found) {
			Assert.assertTrue("" + i, b);
			i++;
		}
	}

	public static class DeclareParamAnnotation {

		@IRClass(ConstructorIR.class)
		public DeclareParamAnnotation(@CustomAnnotation Object param,
				Object param2) {
		}

	}

	/**
	 * Tests if parameter annotations are collected from the parameters.
	 */
	@Test
	public void testCollectParamAnnotation() {
		ClassIR ir = parser.parseClass(DeclareParamAnnotation.class, null);
		ConstructorIR mir = (ConstructorIR) ir.memberIRs.get(Constructor.class)
				.iterator().next();
		Assert.assertEquals(2, mir.getParameterAnnotations().length);
		Assert.assertEquals(1, mir.getParameterAnnotations()[0].size());
		Assert.assertNotNull(mir.getParameterAnnotations()[0]
				.get(CustomAnnotation.class));
		Assert.assertTrue(mir.getParameterAnnotations()[1].isEmpty());
	}

	public static class DeclareTwoParams {
		@IRClass(ConstructorIR.class)
		public DeclareTwoParams(@CustomAnnotation Object param,
				@CustomAnnotation Object param2) {
		}
	}

	public static class ExtendTwoParams extends DeclareTwoParams {
		public ExtendTwoParams(Object param, @AnnotationOverride Object param2) {
			super(param, param2);
		}
	}

	/**
	 * Tests if parameter annotations are kept on override and removed if
	 * <tt>AnnotationOverride</tt> is encountered.
	 */
	@Test
	public void testOverrideParamAnnotation() {
		ClassIR ir = parser.parseClass(ExtendTwoParams.class, null);
		ConstructorIR mir = (ConstructorIR) ir.memberIRs.get(Constructor.class)
				.iterator().next();
		Assert.assertEquals(2, mir.getParameterAnnotations().length);
		Assert.assertEquals(1, mir.getParameterAnnotations()[0].size());
		Assert.assertNotNull(mir.getParameterAnnotations()[0]
				.get(CustomAnnotation.class));
		Assert.assertTrue(mir.getParameterAnnotations()[1].isEmpty());
	}
}
